#!/usr/bin/python
#coding=utf-8

'''
'''
__copyright__ = "Copyright (c) 2019 . All Rights Reserved"
__author__ = "pei_xilong@tongji.edu.cn"
__date__ = "2019-09-10"

__all__ = ['scriptError', 'deal_require', 'sql_StatisticsReporter', 'df_StatisticsReporter', 'stream_StatisticsReporter', 'merge_src']

import os
import sys
curdir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(curdir)

from imp import reload
import traceback
import pymysql
from warnings import catch_warnings, filterwarnings
import ImportCode
import GrammerDict2Py
from SR_Context import SR_Context
import CommonUtils
import Dict2Html
import SR_Utils
import StrUtils
import SQL_Utils

logger = CommonUtils.getLogger('StatisticsReporter')

def scriptError(info, lineno, title='', logger_error=True):
    if logger_error:
        logger_error = logger.error
    else:
        logger_error = logger.info
    logger_error('[行：'+str(lineno)+']' + title)
    if (info == ''):
        if sys.exc_info()[2] is not None:
            info = traceback.format_exc()
    info = StrUtils.strsFormatWithLineno(str(info))
    if info:
        logger_error('\n____0____5___10___15___20___25___30___35___40___45___50___55___60___65___70___75___80_____\n'
                     + info
                     + '^^^^0^^^^5^^^10^^^15^^^20^^^25^^^30^^^35^^^40^^^45^^^50^^^55^^^60^^^65^^^70^^^75^^^80^^^^^\n')


def do_prepare(_src):
    requireDict = _src.get_value('require', defValue={})
    _src.py_module.__dict__.update(requireDict)


def do_final(_src):
    '''
    fun_final=eval('final')
    if (hasattr(fun_final, '__call__')):
        cu.call_sgfun(final)
    '''
    for _src.curY in range(_src.numY):
        _src.py_module.final(_src)
    _src.py_module.gfinal(_src)


def deal_require(var, oldVal):
    return oldVal


def sr_finalAction(_src):
    if _src.dictY:
        # 如果纵轴(Y)方向经过动态Key式条件，则把动态产生条件的内容填回titleY。
        for key, value in _src.dictY.items():
            key = str(key)
            # 格式_N_0__numY 转成 numY
            pos = key.find('__')
            if (pos > 0):
                _src.titlesY[value] = ['.//' + key[pos+2]]
            else:
                _src.titlesY[value] = ['.//' + key]


def df_StatisticsReporter(sr_filename, _src=None, parameters={}, df=None, debug=False, ifDoFinal=SR_Utils.IfDoFinal.DoFinal, fun_require=deal_require, dump=False):
    '''

    :param sr_filename:
    :param _src:
    :param parameters:
    :param df:
    :param debug: 如果为真True，则在生成角本对应的python程序之后把程序文件返回，不再计算角本
    :param ifDoFinal: 是否执行最后final动作
    :param fun_require: 提供require变量的回调函数或者字典(“变量名=值”的集合)
    :param dump: 分析角本中间格式是否dump到_src.dump中，dump的_src.dump数据类型为字符串
    :return:
    '''
    if not _src:
        # 计算环境如果已经初始化，表示当前规则角本(.sr文件)已经执行过，略过角本分析过程
        _src = SR_Context()
        _src.debug = debug
        _src.fun_require = fun_require

        if not StrUtils.isEmpty(df):
            _src.df = df
        else:
            return 1, '', ''

        status, grammer = GrammerDict2Py.dict2Py(_src, sr_filename, parameters)
        if (status != 0):
            scriptError(grammer, sys._getframe().f_lineno, '【角本分析时错】')
            return status, '', _src
        _src.grammer = grammer
        _src.parameters = parameters
        if dump:
            _src.dump = SR_Utils.src_Dump(_src)

    if not StrUtils.isEmpty(df):
        _src.df = df

        callMainY = 1
        try:
            grammer = SQL_Utils.optimizeFieldAccess(_src.grammer, df.columns.tolist())

            _src.py_module, info = ImportCode.importCode(_src, grammer)
            if not _src.py_module:
                scriptError(info, sys._getframe().f_lineno, '【角本加载时错】')
                return 2, grammer, _src

            do_prepare(_src)
            _src.py_module.main(_src)
            mainX = _src.py_module.mainX
            mainY = _src.py_module.mainY

            # convert pandas dataframe (df) to numpy ndarray
            df_values = df.to_numpy()
            '''
            for i in range(len(df)):
                # 遍历Dataframe，通过iloc()要比取df的values(numpy ndarray)，再遍历，慢得多
                #df_row = df.iloc[i]
                df_row = df_values[i]
            '''
            for df_row in df_values:
                CommonUtils.reset(_src)

                # 如果一个Y条件都没有，强行增加一个永真的一行
                if callMainY:
                    retVal = mainY(_src, df_row)
                    if retVal:
                        callMainY = 0
                        _src.numY = 1
                        _src.init_datas()

                        # 填Y0行永真条件时，它的标题也构选一个空的，这不必要
                        #_src.titlesY = {0:['.//']}
                else:
                    CommonUtils.stateY(_src, 0)

                for i, statusY in enumerate(_src.statusY):
                    if statusY:
                        _src.curY = i
                        mainX(_src, df_row)
        except (BaseException) as exc:
            scriptError('', sys._getframe().f_lineno, '【运行时错: '+type(exc).__name__+'】'+str(exc))
            return 1, str(exc), _src

        sr_finalAction(_src)

        # 做final计算，解决象“均值=合计值/计数“这样的结果整理计算
        if (ifDoFinal == SR_Utils.IfDoFinal.DoFinal):
            do_final(_src)

    res = CommonUtils.getResult(_src)
    return 0, res, _src


def sql_StatisticsReporter(sr_filename, _src=None, parameters={}, conn=None, dictResult=True,
                           host='127.0.0.1', user='root', password='', dbname='', params={}, sql='', gb_table='',
                           debug=False, ifDoFinal=SR_Utils.IfDoFinal.DoFinal, fun_require=deal_require, dump=False):
    '''
    如果dump参数为True，则只是把角本分析中间结果dump出来，不执行具体分析事务

    :param sr_filename:
    :param _src:
    :param parameters:
    :param conn: Mysql连接，如果为None，则自己做Mysql连接
    :param dictResult: 数据库游标Cursor是否返回字典
    :param host: 数据库服务器IP地址
    :param user: 数据库用户名
    :param password: 数据库连接密码
    :param dbname: 数据库名
    :param params:  数据库SQL所用参数
    :param sql: SQL语句，如果这个参数不为None，则取代SR角本中的sql定义
    :param gb_table: 分组rgoupby所用的表，如果为空，取sql语句中的第一张表
    :param debug: 如果为真True，则在生成角本对应的python程序之后把程序文件返回，不再计算角本
    :param ifDoFinal: 是否执行最后final动作
    :param fun_require: 提供require变量的回调函数或者字典(“变量名=值”的集合)
    :param dump: 分析角本中间格式是否dump到_src.dump中，dump的_src.dump数据类型为字符串
    :return:
    '''

    # 重复利用数据库连接，只靠conn参数，_src中不携带可复用的数据库连接信息
    try:
        if conn is None:
            if dictResult:
                cursorclass = pymysql.cursors.SSDictCursor
            else:
                cursorclass = pymysql.cursors.SSCursor
            conn_ = pymysql.connect(host = host,
                                    user = user,
                                    password = password,
                                    db = dbname,
                                    charset = 'utf8mb4',
                                    cursorclass = cursorclass)
        else:
            conn_ = conn
        cursor = conn_.cursor()
    except:
        scriptError('', sys._getframe().f_lineno, '【数据库连接错】')
        return 1, '',  None

    if not _src:
        # 计算环境如果已经初始化，表示当前规则角本(.sr文件)已经执行过，略过角本分析过程
        _src = SR_Context()
        _src.debug = debug
        _src.fun_require = fun_require

        _src.cursor = cursor
        _src.conn = conn_

        if gb_table:
            _src.gb_table = gb_table
        _src.cur_sql = sql

        status, grammer = GrammerDict2Py.dict2Py(_src, sr_filename, parameters)
        if (status != 0):
            _src.cursor.close()
            if not conn:
                _src.conn.close()
            scriptError(grammer, sys._getframe().f_lineno, '【角本分析时错】')
            return status, '', _src
        _src.grammer = grammer
        _src.parameters = parameters
        if dump:
            _src.dump = SR_Utils.src_Dump(_src)
            _src.cursor.close()
            if not conn:
                _src.conn.close()
            return 0, '', _src
    else:
        _src.cursor = cursor
        _src.conn = conn_

    if not sql:
        sql = _src.get_value('sql')

    if sql:
        try:
            sql = sql.replace('LIMIT_offset_rows__', '')
            _src.cursor.execute(sql, params)

            if not dictResult:
                grammer = SQL_Utils.optimizeFieldAccess(_src.grammer, _src.cursor)
            else:
                grammer = _src.grammer

            _src.py_module, info = ImportCode.importCode(_src, grammer)
            if not _src.py_module:
                scriptError(info, sys._getframe().f_lineno, '【角本加载时错】')
                return 2, grammer, _src

            callMainY = 1
            try:
                do_prepare(_src)
                _src.py_module.main(_src)
                mainX = _src.py_module.mainX
                mainY = _src.py_module.mainY

                while 1:
                    df_row = cursor.fetchone()
                    if not df_row:
                        break

                    CommonUtils.reset(_src)
                    # 如果一个Y条件都没有，强行增加一个永真的一行
                    if callMainY:
                        retVal = mainY(_src, df_row)
                        if retVal:
                            callMainY = 0
                            _src.numY = 1
                            _src.init_datas()

                            # 填Y0行永真条件时，它的标题也构选一个空的，这不必要
                            #_src.titlesY = {0:['.//']}
                    else:
                        CommonUtils.stateY(_src, 0)

                    for i, statusY in enumerate(_src.statusY):
                        if statusY:
                            _src.curY = i
                            mainX(_src, df_row)
            except (BaseException) as exc:
                scriptError('', sys._getframe().f_lineno, '【运行时错: '+type(exc).__name__+', cursor.rownumber:'+ str(cursor.rownumber) + '】'+str(exc))
                return 1, str(exc), _src
        except Exception as exc:
            info = str(sys.exc_info())
            scriptError('', sys._getframe().f_lineno, '【运行时错】'+info)
            return 1, '', _src
        finally:
            _src.cursor.close()
            if not conn:
                _src.conn.close()

        sr_finalAction(_src)

        # 做final计算，解决象“均值=合计值/计数“这样的结果整理计算
        if (ifDoFinal == SR_Utils.IfDoFinal.DoFinal):
            do_final(_src)

    res = CommonUtils.getResult(_src)
    return 0, res, _src


def stream_StatisticsReporter(sr_filename, _src=None, parameters={}, srd=None, debug=False, ifDoFinal=SR_Utils.IfDoFinal.DoFinal, fun_require=deal_require, dump=False):
    '''
    如果dump参数为True，则只是把角本分析中间结果dump出来，不执行具体分析事务

    :param sr_filename:
    :param sr_name:
    :param parameters:
    :param srd: StreamRegularDict类型，或者list、tuple类型数据
    :param debug: 如果为真True，则在生成角本对应的python程序之后把程序文件返回，不再计算角本
    :param ifDoFinal: 是否执行最后final动作
    :param fun_require: 提供require变量的回调函数或者字典(“变量名=值”的集合)
    :param dump: 分析角本中间格式是否dump到_src.dump中，dump的_src.dump数据类型为字符串
    :return:
    '''
    if not _src:
        # 计算环境如果已经初始化，表示当前规则角本(.sr文件)已经执行过，略过角本分析过程
        _src = SR_Context()
        _src.debug = debug
        _src.fun_require = fun_require

        status, grammer = GrammerDict2Py.dict2Py(_src, sr_filename, parameters)
        if (status != 0):
            scriptError(grammer, sys._getframe().f_lineno, '【角本分析时错】')
            return status, '', _src
        _src.grammer = grammer
        _src.parameters = parameters
        if dump:
            _src.dump = SR_Utils.src_Dump(_src)
            return 0, '', _src

    _src.py_module, info = ImportCode.importCode(_src, _src.grammer)
    if not _src.py_module:
        scriptError(info, sys._getframe().f_lineno, '【角本加载时错】')
        return 2, grammer, _src

    if (srd):
        callMainY = 1
        try:
            do_prepare(_src)
            _src.py_module.main(_src)
            mainX = _src.py_module.mainX
            mainY = _src.py_module.mainY

            num = 0
            if isinstance(srd, (list, tuple)):
                isinstance_srd = 1
            else:
                isinstance_srd = 0

            while 1:
                if isinstance_srd:
                    try:
                        df_row = srd[num]
                        num += 1
                    except:
                        break
                else:
                    status, df_row = srd.readline()
                    if status:
                        break

                if isinstance(df_row, (list, tuple)):
                    for one_row in df_row:
                        CommonUtils.reset(_src)

                        # 如果一个Y条件都没有，强行增加一个永真的一行
                        if callMainY:
                            retVal = mainY(_src, one_row)
                            if retVal:
                                callMainY = 0
                                _src.numY = 1
                                _src.init_datas()

                                # 填Y0行永真条件时，它的标题也构选一个空的，这不必要
                                #_src.titlesY = {0:['.//']}
                        else:
                            CommonUtils.stateY(_src, 0)

                        for i, statusY in enumerate(_src.statusY):
                            if statusY:
                                _src.curY = i
                                mainX(_src, one_row)
                else:
                    CommonUtils.reset(_src)

                    # 如果一个Y条件都没有，强行增加一个永真的一行
                    if callMainY:
                        retVal = mainY(_src, df_row)
                        if retVal:
                            callMainY = 0
                            _src.numY = 1
                            _src.init_datas()

                            # 填Y0行永真条件时，它的标题也构选一个空的，这不必要
                            #_src.titlesY = {0:['.//']}
                    else:
                        CommonUtils.stateY(_src, 0)

                    for i, statusY in enumerate(_src.statusY):
                        if statusY:
                            _src.curY = i
                            mainX(_src, df_row)
        except (BaseException) as exc:
            scriptError('', sys._getframe().f_lineno, '【运行时错: '+type(exc).__name__+'】'+str(exc))
            return 1, str(exc), _src

        sr_finalAction(_src)

        # 做final计算，解决象“均值=合计值/计数“这样的结果整理计算
        if (ifDoFinal == SR_Utils.IfDoFinal.DoFinal):
            do_final(_src)

    res = CommonUtils.getResult(_src)
    return 0, res, _src


def merge_src(_src_master, _src_slaves=None):
    '''

    :param _src_master:
    :param _src_slaves:
    :return:
    '''
    def mergeDictY(_src_master, _src_slave):
        '''
        - 在_src_slave而不在_src_master中的数据，添加
        - 在_src_slave也在_src_master中的数据，用_src_slave覆盖_src_master中的数据
        - 不在_src_slave而在_src_master中的数据，移除
        最后得到可以_src_slave与_src_master相匹配的两个data数组
        '''
        _src_str = SR_Utils.src_Dump(_src_master)
        _src_blank = SR_Utils.src_Load(_src_str)

        for key, y_slave in _src_slave.dictY.items():
            y_master = _src_master.dictY.get(key, -1)
            if (y_master >= 0):
                _src_blank.data[y_master] = _src_slave.data[y_slave]
                _src_blank.extdata[y_master] = _src_slave.extdata[y_slave]
                _src_blank.moredata[y_master] = _src_slave.moredata[y_slave]
            else:
                CommonUtils.stateKeyY(_src_blank, key, nparr_data=_src_slave.data[y_slave],
                                                       nparr_extdata=_src_slave.extdata[y_slave],
                                                       nparr_moredata=_src_slave.moredata[y_slave])
                CommonUtils.stateKeyY(_src_master, key)
        return _src_blank

    if _src_slaves:
        if isinstance(_src_slaves, (list, tuple)):
            for _src in _src_slaves:
                _src_master.registerSlaves(_src)
        else:
            _src_master.registerSlaves(_src_slaves)

    if not _src_master.py_module:
        _src_master.py_module, info = ImportCode.importCode(_src_master, _src_master.grammer)
    merge = _src_master.py_module.merge

    # 把_src_master.slaves一个一个合并到_src_master上去
    slaves = _src_master.slaves
    for _src in slaves:
        _src_slave = mergeDictY(_src_master, _src)
        _src_master.slaves = [_src_slave]
        for _src_master.curY in range(_src_master.numY):
            merge(_src_master)
    _src_master.slaves = slaves

    sr_finalAction(_src_master)
    do_final(_src_master)
